package com.example.rule.rule;


import com.example.rule.db.entity.PymtRule;
import com.example.rule.db.entity.PymtRuleType;
import com.example.rule.action.CashAction;
import com.example.rule.action.RateAction;
import com.example.rule.condition.ConditionAdaptor;
import com.example.rule.condition.factory.AbstractConditionFactory;
import com.example.rule.constant.RuleConstant;
import com.example.rule.core.Action;
import com.example.rule.core.Condition;
import com.example.rule.core.Facts;
import com.example.rule.core.Rule;
import com.example.rule.enums.ChargeTypeEnum;
import com.example.rule.enums.SelectorEnum;
import com.example.rule.expression.factory.AbstractRepFactory;
import com.example.rule.util.CacheUtil;
import lombok.extern.slf4j.Slf4j;

import java.math.BigDecimal;
import java.text.MessageFormat;
import java.time.ZonedDateTime;
import java.util.Objects;

/**
 * @author Jason - Chusifeng
 * @version 1.0.0
 * @ClassName BasicRule.java
 * @PackageName com\welab\vb\payment\transaction\rule\rule\BasicRule.java
 * @Description rule的基础实现
 * @createTime 2022年10月10日 10:41:29
 */
@Slf4j
public class BasicRule implements Rule {

    /**
     * Rule name.
     */
    protected String name;

    /**
     * Rule description.
     */
    protected String description;

    /**
     * Rule priority.
     */
    protected int priority;


    protected ZonedDateTime createTime;
    /**
     * Create a new {@link BasicRule}.
     */
    public BasicRule() {
        this(Rule.DEFAULT_NAME, Rule.DEFAULT_DESCRIPTION, Rule.DEFAULT_PRIORITY);
    }

    /**
     * Create a new {@link BasicRule}.
     *
     * @param name rule name
     */
    public BasicRule(final String name) {
        this(name, Rule.DEFAULT_DESCRIPTION, Rule.DEFAULT_PRIORITY);
    }

    /**
     * Create a new {@link BasicRule}.
     *
     * @param name rule name
     * @param description rule description
     */
    public BasicRule(final String name, final String description) {
        this(name, description, Rule.DEFAULT_PRIORITY);
    }

    /**
     * Create a new {@link BasicRule}.
     *
     * @param name rule name
     * @param description rule description
     * @param priority rule priority
     */
    public BasicRule(final String name, final String description, final int priority) {
        this.name = name;
        this.description = description;
        this.priority = priority;
    }

    /**
     * {@inheritDoc}
     */
    public boolean evaluate(Facts facts) {
        return false;
    }

    /**
     * {@inheritDoc}
     */
    public BigDecimal execute(Facts facts) throws Exception {
        // no op
        return BigDecimal.ZERO;
    }

    public String getName() {
        return name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(final String description) {
        this.description = description;
    }

    public int getPriority() {
        return priority;
    }

    public void setPriority(final int priority) {
        this.priority = priority;
    }

    public ZonedDateTime getCreateTime(){
        return createTime;
    }

    public void setCreateTime(ZonedDateTime createTime) {
        this.createTime = createTime;
    }
    @Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        BasicRule basicRule = (BasicRule) o;

        if (priority != basicRule.priority) {
            return false;
        }
        if (!name.equals(basicRule.name)) {
            return false;
        }
        return Objects.equals(description, basicRule.description);

    }

    @Override
    public int hashCode() {
        int result = name.hashCode();
        result = 31 * result + (description != null ? description.hashCode() : 0);
        result = 31 * result + priority;
        return result;
    }

    @Override
    public String toString() {
        return name;
    }

    @Override
    public int compareTo(final Rule rule) {
        if (getPriority() < rule.getPriority()) {
            return -1;
        } else if (getPriority() > rule.getPriority()) {
            return 1;
        } else {
           //如果优先级相同，则按照规则创建时间先后
            return getCreateTime().isBefore(rule.getCreateTime()) ? -1:1;
        }
    }
    /**
     * @author Jason - Chusifeng
     * @Description
     * @version: v1.8.0
     * @date 11:30 2022/10/11
     * @param pymtRule 数据库存储的一条设定的规则数据
     * @return 系统一条规则
     **/
    public static Rule createRule(PymtRule pymtRule) {
        ConditionAdaptor adaptor = new ConditionAdaptor();
        AbstractConditionFactory abstractConditionFactory = RuleConstant.CONFIG_COND.get(pymtRule.getConditionName());
        Integer id = CacheUtil.getRuleTypes().stream()
                .filter(ruleType -> ruleType.getPkPymtRuleType().equals(pymtRule.getType()))
                .findFirst().map(PymtRuleType::getValue).get();
        AbstractRepFactory repFactory = RuleConstant.CONFIG_REP.get(id);
        adaptor.setExpression(repFactory.createRepresent());
        Condition condition = abstractConditionFactory.createCondition();
        adaptor.setCondition(condition);
        String[] operators = adaptor.createExpressStr(pymtRule);
        for (String expression : operators) {
            condition.addExpression(expression);
        }
        //设置条件之间的关系
        condition.setSelector(SelectorEnum.getInstance(pymtRule.getConnector()));
        CommonRule elRule = new CommonRule(pymtRule.getName(), condition, createAction(pymtRule.getChargeType(), pymtRule.getChargeValue()));
        elRule.setPriority(pymtRule.getPriority());
        elRule.setCreateTime(ZonedDateTime.now());
        return elRule;
    }


    /**
     * @param chargeType  金额类型
     * @param chargeValue 类型对应的数值
     * @return 创建好的执行动作
     * @author Jason - Chusifeng
     * @Description
     * @version: v1.8.0
     * @date 14:18 2022/10/10
     **/
    private static Action createAction(String chargeType, String chargeValue) {
        //处理 action
        Action action;
        if (ChargeTypeEnum.RATE.getValue().equals(chargeType)) {
            action = new RateAction(MessageFormat.format("#total * {0}", chargeValue));
        } else {
            action = new CashAction(new BigDecimal(chargeValue));
        }
        return action;
    }

}
